/*
 * Copyright (C) 2011 by ayoung <ayoung.p@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.peaceb.ruletherain;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationManager;
import android.os.IBinder;

public class MockLocationService extends Service implements Runnable {
    public static final String KEY_LATITUDE  = "latitude";
    public static final String KEY_LONGITUDE = "longitude";
    public static final String KEY_ALTITUDE  = "altitude";
    public static final String KEY_CITY_NAME = "city_name";
    
    private static final int LOCATION_UPDATE_INTERVAL = 3; /* sec */
    private static final int NOTI_ID = 0;
    private static final String MOCK_PROVIDER        = "rtr_mock_privider";
    private static final String OLD_MOCK_PROVIDER    = "mock_privider";
//    private static final String PASSIVE_PROVIDER     = "passive";
    private static final String GPS_PROVIDER         = LocationManager.GPS_PROVIDER;
    private static final String NETWORK_PROVIDER     = LocationManager.NETWORK_PROVIDER;
    
    private boolean mStop = true;
    private Thread  mThread;
    
    private double mLatitude;
    private double mLongitude;
    private double mAltitude;
    private String mCityName;
    
    private boolean mStartCommandCalled = false;
    
    @Override
    public IBinder onBind(Intent intent) {
        Trace.i(this, "onBind");
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Trace.i(this, "onCreate");
        mStartCommandCalled = false;
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Trace.e(e.toString());
                }
                
                if (!mStartCommandCalled) {
                    Trace.i(this, "call onStartCommand");
                    Trace.toast(MockLocationService.this, "Mock location service restarted");
                    onStartCommand(null, 0, 0);
                }
            }
        }).start();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Trace.i(this, "onDestroy");
        mStop = true;
        cleanup();
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        Trace.i(this, "onstart");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Trace.i(this, "onStartCommand");
        mStartCommandCalled = true;
        
        if (intent == null) {
            if (loadLastLocation() == C.ERROR) {
                stopSelf();
                return START_NOT_STICKY;
            }
        }
        else {
            mLatitude  = intent.getDoubleExtra(KEY_LATITUDE, 0);
            mLongitude = intent.getDoubleExtra(KEY_LONGITUDE, 0);
            mAltitude  = intent.getDoubleExtra(KEY_ALTITUDE, 0);
            mCityName  = intent.getStringExtra(KEY_CITY_NAME);
            saveLastLocation();
        }
        
        mStop = false;
        if (mThread == null) {
            mThread = new Thread(this);
            mThread.start();
        }
        
        showNotification(mCityName, mLatitude, mLongitude);
        
        return Service.START_STICKY;
    }
    
    @Override
    public void onLowMemory() {
        super.onLowMemory();
        Trace.i(this, "onLowMemory");
    }

    @Override
    public void run() {
        HashMap<String, Location> locationMap = new HashMap<String, Location>();
        
        LocationManager lm = (LocationManager)getSystemService(LOCATION_SERVICE);
        
        if (lm.getProvider(MOCK_PROVIDER) != null) {
            lm.removeTestProvider(MOCK_PROVIDER);
        }
        
        for (String provider : getTargetProviders(lm)) {
            try {
                if (MOCK_PROVIDER.equals(provider) || lm.isProviderEnabled(provider)) {
                    lm.addTestProvider(provider, false, false, false, false, false, false, false, 
                            android.location.Criteria.POWER_LOW, android.location.Criteria.ACCURACY_FINE);
                    lm.setTestProviderEnabled(provider, true);
                    locationMap.put(provider, new Location(provider));
                }
            }
            catch (SecurityException e) {
                Trace.e(e.toString());
                stopSelf();
                return;
            }
            catch (IllegalArgumentException e) {
                Trace.e(e.toString());
                stopSelf();
            }
        }
        
        int count = LOCATION_UPDATE_INTERVAL;
        while (!mStop) {
            if (count >= LOCATION_UPDATE_INTERVAL) {
                for (String provider : locationMap.keySet()) {
                    Location location = locationMap.get(provider);
                    if (location == null) {
                        Trace.w("Location for " + provider + " is not exist");
                        continue;
                    }
                    location.setLatitude(mLatitude);
                    location.setLongitude(mLongitude);
                    location.setAltitude(mAltitude);
                    location.setTime(System.currentTimeMillis());
                    
                    try {
                        if (lm.isProviderEnabled(provider)) {
                            lm.setTestProviderLocation(provider, location);
                        }
                    }
                    catch (SecurityException e) {
                        Trace.e(e.toString());
                        /* Maybe "Allow mock location" was changed while service is running */ 
                        stopSelf();
                        return;
                    }
                    catch (IllegalArgumentException e) {
                        Trace.e(e.toString());
                        stopSelf();
                        return;
                    }
                }
                
                count = 0;
                Trace.i("Location updated");
            }
            count++;
            
            try {
                Thread.sleep(1000);
            } 
            catch (InterruptedException e) {
                Trace.e(this, e.toString());
            }
        }
        
        Trace.i(this, "Thread ended");
    }
    
    /**
     * clean up
     */
    private void cleanup() {
        hideNotification();
        LocationManager lm = (LocationManager)getSystemService(LOCATION_SERVICE);
        
        for (String provider : getTargetProviders(lm)) {
            try {
                if (MOCK_PROVIDER.equals(provider) || lm.isProviderEnabled(provider)) {
                    lm.clearTestProviderEnabled(provider);
                    lm.clearTestProviderLocation(provider);
                    lm.clearTestProviderStatus(provider);
                    lm.removeTestProvider(provider);
                }
            }
            catch (SecurityException e) {
                Trace.w(e.toString());
                Util.showToast(this, R.string.check_setting);
                return;
            }
            catch (IllegalArgumentException e) {
                Trace.w(e.toString());
                continue;
            }
        }
        
        Util.showToast(this, R.string.service_ended);
    }
    
    /**
     * Get target provider list to mock.
     * 
     * @param lm
     * @return
     */
    private List<String> getTargetProviders(LocationManager lm) {
        ArrayList<String> list = new ArrayList<String>();
        
        for (String provider : lm.getAllProviders()) {
            if (GPS_PROVIDER.equals(provider) || NETWORK_PROVIDER.equals(provider)) {
                if (lm.isProviderEnabled(provider)) {
                    list.add(provider);
                }
            }
        }
        
        if (list.size() == 0) {
            list.add(MOCK_PROVIDER);
        }
        
        return list;
    }
    
    /**
     * Save last location to file for load location when a service is restarted by system.
     */
    private void saveLastLocation() {
        SharedPreferences pref = getSharedPreferences(C.PREFRENCE_NAME, MODE_PRIVATE);
        SharedPreferences.Editor editor = pref.edit();
        editor.putString(KEY_LATITUDE, Double.toString(mLatitude));
        editor.putString(KEY_LONGITUDE, Double.toString(mLongitude));
        editor.putString(KEY_ALTITUDE, Double.toString(mAltitude));
        editor.putString(KEY_CITY_NAME, mCityName);
        editor.commit();
    }
    
    /**
     * Load last location when a service is restarted by system.
     * 
     * @return
     */
    private int loadLastLocation() {
        SharedPreferences pref = getSharedPreferences(C.PREFRENCE_NAME, MODE_PRIVATE);
        mLatitude  = Double.valueOf(pref.getString(KEY_LATITUDE, "0"));
        mLongitude = Double.valueOf(pref.getString(KEY_LONGITUDE, "0"));
        mAltitude  = Double.valueOf(pref.getString(KEY_ALTITUDE, "0"));
        mCityName  = pref.getString(KEY_CITY_NAME, null);

        if (mCityName == null) {
            return C.ERROR;
        }
        
        return C.SUCCESS;
    }
    
    /**
     * Show notification
     * 
     * @param cityName
     * @param latitude
     * @param longitude
     */
    private void showNotification(String cityName, double latitude, double longitude) {
        NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        Notification noti = new Notification(R.drawable.noti_icon,
                getText(R.string.location_changed) + " " + cityName, 
                System.currentTimeMillis());
        Intent notiIntent = new Intent(this, RuleTheRainActivity.class);
        notiIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notiIntent, 0);
        
        noti.setLatestEventInfo(this, 
                getText(R.string.app_name) + " : " + cityName, 
                String.format("(%f, %f)", latitude, longitude), 
                contentIntent);
        
        noti.flags |= Notification.FLAG_ONGOING_EVENT;
        
        nm.notify(NOTI_ID, noti);
    }
    
    /**
     * Hide notification
     */
    private void hideNotification() {
        NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        nm.cancel(NOTI_ID);
    }
    
    /**
     * This method will check  a mock location that was added by old version. (before 1.2.0)
     */
    public static void checkOldProvider(final Context context) {
        final LocationManager lm = (LocationManager)context.getSystemService(LOCATION_SERVICE);
        if (lm.getProvider(OLD_MOCK_PROVIDER) != null) {
            Trace.w(OLD_MOCK_PROVIDER + " is exist. It need to remove");
            new AlertDialog.Builder(context)
                .setIcon(android.R.drawable.ic_dialog_alert)
                .setTitle(R.string.notice)
                .setMessage(R.string.remove_old_provider)
                .setNegativeButton(android.R.string.no, null)
                .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (lm.getProvider(OLD_MOCK_PROVIDER) != null) {
                            lm.removeTestProvider(OLD_MOCK_PROVIDER);
                            Util.showToast(context, R.string.mock_provider_removed);
                        }
                    }
                }).show();
            
        }
    }
}
